{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Building a Agent API with LangServe: Integrating Currency Exchange and Trip Planning\n",
    "\n",
    "- Author: [Hwayoung Cha](https://github.com/forwardyoung)\n",
    "- Peer Review: []()\n",
    "- Proofread : [JaeJun Shim](https://github.com/kkam-dragon)\n",
    "- This is a part of [LangChain Open Tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n",
    "\n",
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/19-Cookbook/08-Serving/03-LangServe-Agent-API.ipynb)[![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/19-Cookbook/08-Serving/03-LangServe-Agent-API.ipynb)\n",
    "## Overview\n",
    "\n",
    "This tutorial guides you through creating a Agent API using ```LangServe```, enabling you to build intelligent and dynamic applications. You'll learn how to leverage LangChain agents and deploy them as production-ready APIs with ease. Discover how to define tools, orchestrate agent workflows, and expose them via a simple and scalable REST interface.\n",
    "\n",
    "### Table of Contents\n",
    "\n",
    "- [Overview](#overview)\n",
    "- [Environement Setup](#environment-setup)\n",
    "- [LangServe](#langserve)\n",
    "- [Implementing a Travel Planning Agent](#implementing-a-travel-planning-agent)\n",
    "- [Implementing a Currency exchange agent](#implementing-a-currency-exchange-agent)\n",
    "- [Testing in the LangServe Playground](#testing-in-the-langserve-playground)\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## References\n",
    "\n",
    "- [LangServe](https://python.langchain.com/docs/langserve/)\n",
    "- [FreecurrencyAPI](https://freecurrencyapi.com/docs/)\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Environment Setup\n",
    "\n",
    "Set up the environment. You may refer to [Environment Setup](https://wikidocs.net/257836) for more details.\n",
    "\n",
    "**[Note]**\n",
    "- ```langchain-opentutorial``` is a package that provides a set of easy-to-use environment setup, useful functions and utilities for tutorials. \n",
    "- You can checkout the [```langchain-opentutorial```](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n",
      "[notice] A new release of pip is available: 24.3.1 -> 25.0.1\n",
      "[notice] To update, run: python.exe -m pip install --upgrade pip\n"
     ]
    }
   ],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install langchain-opentutorial sse_starlette uvicorn"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install required packages\n",
    "from langchain_opentutorial import package\n",
    "\n",
    "package.install(\n",
    "    [   \"langchain_openai\",\n",
    "        \"langserve\",\n",
    "        \"sse_starlette\",\n",
    "        \"uvicorn\"\n",
    "    ],\n",
    "    verbose=False,\n",
    "    upgrade=False,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can alternatively set API keys in .env file and load it.\n",
    "\n",
    "[Note] This is not necessary if you've already set API keys in previous steps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Environment variables have been set successfully.\n"
     ]
    }
   ],
   "source": [
    "# Set environment variables\n",
    "from langchain_opentutorial import set_env\n",
    "\n",
    "set_env(\n",
    "    {\n",
    "        \"OPENAI_API_KEY\": \"\",\n",
    "        \"FREECURRENCY_API_KEY\": \"\"\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## LangServe\n",
    "\n",
    "LangServe is a tool that allows you to easily deploy LangChain runnables and chains as REST APIs. It integrates with FastAPI and uses Pydantic for data validation."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Implementing a Travel Planning Agent\n",
    "\n",
    "This section demonstrates how to implement a travel planning agent. This agent suggests customized travel plans based on the user's travel requirements."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.agents import AgentExecutor, create_openai_functions_agent\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "from langchain.tools import tool\n",
    "from langserve import add_routes\n",
    "from fastapi import FastAPI\n",
    "from typing import List, Optional\n",
    "from pydantic import BaseModel, Field\n",
    "\n",
    "# Define input/output models\n",
    "class TravelPlanRequest(BaseModel):\n",
    "    \"\"\"Travel planning request structure\"\"\"\n",
    "    destination: str = Field(..., description=\"City or country to visit\")\n",
    "    duration: int = Field(..., description=\"Number of days for the trip\")\n",
    "    interests: List[str] = Field(\n",
    "        default_factory=list,\n",
    "        description=\"List of interests (e.g., ['food', 'culture', 'history'])\"\n",
    "    )\n",
    "\n",
    "class TravelPlanResponse(BaseModel):\n",
    "    \"\"\"Travel planning response structure\"\"\"\n",
    "    itinerary: List[str]\n",
    "    recommendations: List[str]\n",
    "    estimated_budget: str\n",
    "\n",
    "@tool\n",
    "def get_travel_suggestions(destination: str, duration: int, interests: str) -> str:\n",
    "    \"\"\"Generates travel suggestions based on the destination, duration, and interests.\"\"\"\n",
    "    # In a real implementation, you might use a travel API or database\n",
    "    return f\"Here's a {duration}-day itinerary for {destination} focusing on {interests}...\"\n",
    "\n",
    "llm = ChatOpenAI(model=\"gpt-4.0\")\n",
    "prompt = ChatPromptTemplate.from_messages([\n",
    "    (\"system\", \"You are a helpful travel planning assistant.\"),\n",
    "    (\"human\", \"Plan a trip to {destination} for {duration} days with interests in {interests}\"),\n",
    "    MessagesPlaceholder(variable_name=\"agent_scratchpad\")\n",
    "])\n",
    "tools = [get_travel_suggestions]\n",
    "\n",
    "agent = create_openai_functions_agent(llm, tools, prompt)\n",
    "travel_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)\n",
    "\n",
    "app = FastAPI()\n",
    "add_routes(\n",
    "    app,\n",
    "    travel_executor,\n",
    "    path=\"/travel-planner\",\n",
    "    input_type=TravelPlanRequest,\n",
    "    output_type=TravelPlanResponse\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Implementing a Currency exchange agent\n",
    "\n",
    "This section shows how to implement a currency exchange agent. This agent performs currency conversions using real-time exchange rate information."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import requests\n",
    "from pydantic import BaseModel, Field, field_validator\n",
    "from typing import Optional\n",
    "from datetime import datetime\n",
    "\n",
    "class CurrencyExchangeRequest(BaseModel):\n",
    "    \"\"\"Currency exchange request structure\"\"\"\n",
    "    amount: float = Field(..., description=\"Amount to convert\")\n",
    "    from_currency: str = Field(..., description=\"Source currency code (e.g., USD)\")\n",
    "    to_currency: str = Field(..., description=\"Target currency code (e.g., EUR)\")\n",
    "\n",
    "    @field_validator('amount')\n",
    "    def amount_must_be_positive(cls, v):\n",
    "        if v <= 0:\n",
    "            raise ValueError('Amount must be positive')\n",
    "        return v\n",
    "\n",
    "    @field_validator('from_currency', 'to_currency')\n",
    "    def currency_must_be_valid(cls, v):\n",
    "        if len(v) != 3:\n",
    "            raise ValueError('Currency code must be 3 characters')\n",
    "        return v.upper()\n",
    "\n",
    "class CurrencyExchangeResponse(BaseModel):\n",
    "    \"\"\"Currency exchange response structure\"\"\"\n",
    "    converted_amount: float\n",
    "    exchange_rate: float\n",
    "    timestamp: str\n",
    "    from_currency: str\n",
    "    to_currency: str\n",
    "\n",
    "API_KEY = os.getenv(\"FREECURRENCY_API_KEY\")\n",
    "\n",
    "@tool\n",
    "def get_exchange_rate(from_currency: str, to_currency: str) -> float:\n",
    "    \"\"\"Gets the current exchange rate between two currencies.\"\"\"\n",
    "    url = f\"https://api.freecurrencyapi.com/v1/latest\"\n",
    "    params = {\n",
    "        \"apikey\": API_KEY,\n",
    "        \"base_currency\": from_currency,\n",
    "        \"currencies\": to_currency\n",
    "    }\n",
    "    response = requests.get(url, params=params)\n",
    "    data = response.json()\n",
    "    return data['data'][to_currency]\n",
    "\n",
    "llm = ChatOpenAI(model=\"gpt-4.0\")\n",
    "prompt = ChatPromptTemplate.from_messages([\n",
    "    (\"system\", \"You are a helpful currency exchange assistant.\"),\n",
    "    (\"human\", \"Convert {amount} {from_currency} to {to_currency}\"),\n",
    "    MessagesPlaceholder(variable_name=\"agent_scratchpad\")\n",
    "])\n",
    "tools = [get_exchange_rate]\n",
    "\n",
    "agent = create_openai_functions_agent(llm, tools, prompt)\n",
    "currency_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)\n",
    "\n",
    "add_routes(\n",
    "    app,\n",
    "    currency_executor,\n",
    "    path=\"/currency-exchange\",\n",
    "    input_type=CurrencyExchangeRequest,\n",
    "    output_type=CurrencyExchangeResponse\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Testing in the LangServe Playground\n",
    "\n",
    "LangServe provides a playground for easily testing the implemented agents. This allows you to directly verify and debug the API's behavior."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:     Started server process [25888]\n",
      "INFO:     Waiting for application startup.\n",
      "INFO:     Application startup complete.\n",
      "INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "     __          ___      .__   __.   _______      _______. _______ .______     ____    ____  _______\n",
      "    |  |        /   \\     |  \\ |  |  /  _____|    /       ||   ____||   _  \\    \\   \\  /   / |   ____|\n",
      "    |  |       /  ^  \\    |   \\|  | |  |  __     |   (----`|  |__   |  |_)  |    \\   \\/   /  |  |__\n",
      "    |  |      /  /_\\  \\   |  . `  | |  | |_ |     \\   \\    |   __|  |      /      \\      /   |   __|\n",
      "    |  `----./  _____  \\  |  |\\   | |  |__| | .----)   |   |  |____ |  |\\  \\----.  \\    /    |  |____\n",
      "    |_______/__/     \\__\\ |__| \\__|  \\______| |_______/    |_______|| _| `._____|   \\__/     |_______|\n",
      "    \n",
      "\u001b[1;32;40mLANGSERVE:\u001b[0m Playground for chain \"/currency-exchange/\" is live at:\n",
      "\u001b[1;32;40mLANGSERVE:\u001b[0m  │\n",
      "\u001b[1;32;40mLANGSERVE:\u001b[0m  └──> /currency-exchange/playground/\n",
      "\u001b[1;32;40mLANGSERVE:\u001b[0m\n",
      "\u001b[1;32;40mLANGSERVE:\u001b[0m Playground for chain \"/travel-planner/\" is live at:\n",
      "\u001b[1;32;40mLANGSERVE:\u001b[0m  │\n",
      "\u001b[1;32;40mLANGSERVE:\u001b[0m  └──> /travel-planner/playground/\n",
      "\u001b[1;32;40mLANGSERVE:\u001b[0m\n",
      "\u001b[1;32;40mLANGSERVE:\u001b[0m See all available routes at /docs/\n"
     ]
    }
   ],
   "source": [
    "import nest_asyncio\n",
    "import uvicorn\n",
    "\n",
    "nest_asyncio.apply()\n",
    "\n",
    "uvicorn.run(app)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-opentutorial-NKh5zoXg-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
